﻿using System.Security.Claims;
using Devonline.AspNetCore;
using Devonline.Communication.Messages;
using IdentityModel;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

#if PostgreSQL
using Devonline.Database.PostgreSQL;
#elif MySQL
using Devonline.Database.MySQL;
#elif SqlServer
using Devonline.Database.SqlServer;
#endif

namespace Devonline.Identity;

/// <summary>
/// 认证类扩展方法
/// </summary>
public static class IdentityExtensions
{
    /// <summary>
    /// 将认证相关默认设置放入依赖注入容器
    /// </summary>
    /// <param name="services"></param>
    /// <returns></returns>
    public static IServiceCollection AddDefaultIdentity(this IServiceCollection services)
    {
        services.AddIdentity<User, Role>(options =>
        {
            // Password settings.
            options.Password.RequireDigit = true;
            options.Password.RequireLowercase = true;
            options.Password.RequireNonAlphanumeric = true;
            options.Password.RequireUppercase = true;
            options.Password.RequiredLength = 8;
            options.Password.RequiredUniqueChars = 1;

            // Lockout settings.
            options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromDays(1);
            options.Lockout.MaxFailedAccessAttempts = 5;
            options.Lockout.AllowedForNewUsers = true;

            // User settings.
            options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
            options.User.RequireUniqueEmail = false;

            // Identity Store.
            options.Stores.MaxLengthForKeys = 36;
            //options.Stores.ProtectPersonalData = true;
        })
            .AddEntityFrameworkStores<IdentityDbContext>()
            .AddErrorDescriber<DefaultIdentityErrorDescriber>()
            .AddPersonalDataProtection<AesProtector, DefaultKeyRing>()
            .AddDefaultTokenProviders();

        services.TryAddScoped<IdentityDbContext>();
        services.TryAddScoped<UserStore>();
        services.TryAddScoped<RoleStore>();
        services.TryAddScoped<GroupStore>();
        services.TryAddScoped<ResourceAccessStore>();
        services.TryAddScoped<DefaultIdentityErrorDescriber>();
        services.TryAddScoped<IDataService<UserClaim, int>, DataService<UserClaim, int>>();
        return services;
    }

    /// <summary>
    /// 注册默认的基于字符串主键的实体数据对象模型数据操作相关服务
    /// </summary>
    /// <param name="services"></param>
    /// <returns></returns>
    public static IServiceCollection AddIdentityDataService(this IServiceCollection services)
    {
        services.AddScoped(typeof(IDataService<>), typeof(DataService<>));
        services.AddScoped(typeof(IAttachmentService), typeof(AttachmentService));
        services.AddScoped(typeof(IDataWithAttachmentService<>), typeof(DataWithAttachmentService<>));
        services.AddScoped(typeof(IDataWithCollectionService<,>), typeof(DataWithCollectionService<,>));
        services.AddScoped(typeof(IDataWithCollectionAndAttachmentService<,>), typeof(DataWithCollectionAndAttachmentService<,>));
        return services;
    }
    /// <summary>
    /// 注册默认的授权服务
    /// </summary>
    /// <param name="services">依赖注入容器服务</param>
    /// <returns></returns>
    public static IServiceCollection AddDefaultAuthorization(this IServiceCollection services)
    {
        // TODO 使用 AuthorizationFilter 的过滤器机制的话, 就不需要使用基于策略的授权机制了, 两者二选一
        var provider = services.BuildServiceProvider();
        var authorizationService = provider.GetService<AuthorizationService>();
        ArgumentNullException.ThrowIfNull(authorizationService);
        services.AddTransient<IAuthorizationHandler, AuthorizationRequirement>();
        services.AddTransient<IAuthorizationRequirement, AuthorizationRequirement>();
        services.AddAuthorization(options => options.AddPolicy(AppSettings.DEFAULT_AUTHORIZATION_POLICY, policy => policy.AddRequirements(new AuthorizationRequirement(authorizationService))));
        return services;
    }

    /// <summary>
    /// TODO TBD 数据保护功能待验证
    /// </summary>
    /// <param name="services"></param>
    /// <param name="dataProtection"></param>
    /// <returns></returns>
    public static IServiceCollection AddDataProtection(this IServiceCollection services, DataProtectionOptions? dataProtection = default)
    {
        if (dataProtection is not null && !string.IsNullOrWhiteSpace(dataProtection.ApplicationName) && !string.IsNullOrWhiteSpace(dataProtection.Redis))
        {
            services.AddSingleton(dataProtection);
            services.AddDataProtection(x => x.ApplicationDiscriminator = dataProtection.ApplicationDiscriminator)
            .SetApplicationName(dataProtection.ApplicationName)
            //.ProtectKeysWithCertificate(new X509Certificate2(appSetting.Certificate.Path, appSetting.Certificate.Password))
            .PersistKeysToStackExchangeRedis(StackExchange.Redis.ConnectionMultiplexer.Connect(dataProtection.Redis), dataProtection.KeyName);
        }

        return services;
    }

    /// <summary>
    /// 注册默认 MVC 控制器
    /// </summary>
    /// <param name="services">依赖注入容器服务</param>
    /// <param name="httpSetting">配置项</param>
    /// <returns></returns>
    public static IMvcBuilder AddIdentityControllers(this IServiceCollection services, HttpSetting httpSetting)
    {
        return services.AddControllers(options =>
        {
            options.Filters.Add<ExceptionFilter>();
            if (httpSetting.EnableIdentity)
            {
                options.Filters.Add<AuthorizationFilter>();
            }
        }).AddJsonOptions(httpSetting.DateTimeToString);
    }
    /// <summary>
    /// 注册默认 MVC 控制器及页面
    /// </summary>
    /// <param name="services">依赖注入容器服务</param>
    /// <param name="httpSetting">配置项</param>
    /// <returns></returns>
    public static IMvcBuilder AddIdentityControllersWithViews(this IServiceCollection services, HttpSetting httpSetting)
    {
        return services.AddControllersWithViews(options =>
        {
            options.Filters.Add<ExceptionFilter>();
            if (httpSetting.EnableIdentity)
            {
                options.Filters.Add<AuthorizationFilter>();
            }
        }).AddJsonOptions(httpSetting.DateTimeToString);
    }

    /// <summary>
    /// 注册默认服务
    /// </summary>
    /// <param name="services">依赖注入容器服务</param>
    /// <param name="appSetting">配置项</param>
    /// <returns></returns>
    public static IServiceCollection AddDefaultIdentityServices(this IServiceCollection services, AppSetting appSetting)
    {
        services.AddDefaultServices(appSetting);
        services.AddTransient<AuthorizationService>();
        services.AddHttpClient<IdentityService>();
        return services;
    }

    /// <summary>
    /// 针对字符串类型主键的 IIdentity 实例获取对应的 IdentityType
    /// </summary>
    /// <param name="identity"></param>
    /// <returns></returns>
    public static IdentityType GetIdentityType(this IIdentity<string> identity) => identity.GetIdentityType<string>();
    /// <summary>
    /// 针对 IIdentity<TKey> 实例获取对应的 IdentityType
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    /// <param name="identity"></param>
    /// <returns></returns>
    public static IdentityType GetIdentityType<TKey>(this IIdentity<TKey> identity) where TKey : IConvertible, IEquatable<TKey> => identity.GetIdentityType<IIdentity<TKey>, TKey>();
    /// <summary>
    /// 针对任意引用类型获取对应的身份 IdentityType
    /// </summary>
    /// <typeparam name="TIdentity"></typeparam>
    /// <param name="identity"></param>
    /// <returns></returns>
    public static IdentityType GetIdentityType<TIdentity, TKey>(this TIdentity identity) where TIdentity : class, IIdentity<TKey> where TKey : IConvertible, IEquatable<TKey> => identity switch
    {
        User<TKey> => IdentityType.User,
        Role<TKey> => IdentityType.Role,
        Group<TKey> => IdentityType.Group,
        Level<TKey> => IdentityType.Level,
        _ => IdentityType.System
    };

    /// <summary>
    /// 根据身份选择过滤身份结果
    /// </summary>
    /// <typeparam name="TIdentity"></typeparam>
    /// <param name="identities"></param>
    /// <param name="identityTypes"></param>
    /// <returns></returns>
    public static IEnumerable<TIdentity> GetByIdentityType<TIdentity>(this IEnumerable<TIdentity> identities, Dictionary<IdentityType, string[]>? identityTypes = default) where TIdentity : class, IIdentity<string> => identities.GetByIdentityType<TIdentity, string>(identityTypes);
    /// <summary>
    /// 根据身份选择过滤身份结果
    /// </summary>
    /// <typeparam name="TIdentity"></typeparam>
    /// <typeparam name="TKey"></typeparam>
    /// <param name="identities"></param>
    /// <param name="identityTypes"></param>
    /// <returns></returns>
    public static IEnumerable<TIdentity> GetByIdentityType<TIdentity, TKey>(this IEnumerable<TIdentity> identities, Dictionary<IdentityType, string[]>? identityTypes = default) where TIdentity : class, IIdentity<TKey> where TKey : IConvertible, IEquatable<TKey>
    {
        if (identities.Any() && identityTypes is not null)
        {
            var identityType = identities.First().GetIdentityType<TIdentity, TKey>();
            if (identityTypes.ContainsKey(identityType))
            {
                var results = identityTypes.FirstOrDefault(a => a.Key == identityType).Value;
                if (results is not null && results.Length > 0)
                {
                    identities = identities.Where(x => results.Contains(x.Name));
                }
            }
        }

        return identities;
    }

    /// <summary>
    /// 从 ResourceTrees 中找到当前系统的权限树
    /// </summary>
    /// <param name="resourceTree">系统全部权限树</param>
    /// <param name="topId">当前系统的顶级节点资源编号</param>
    public static ResourceViewModel? GetResourceTree(this ICollection<ResourceViewModel> resourceTree, string topId)
    {
        foreach (var resource in resourceTree)
        {
            if (resource.Id == topId)
            {
                return resource;
            }

            if (resource.Children is not null && resource.Children.Count > 0)
            {
                var result = resource.Children.GetResourceTree(topId);
                if (result is not null)
                {
                    return result;
                }
            }
        }

        return null;
    }
    /// <summary>
    /// 过滤权限树, 只保留可在页面显示部分
    /// </summary>
    /// <param name="resource"></param>
    public static void CleanResourceTree(this ResourceViewModel resource)
    {
        if (resource.Children is not null && resource.Children.Count != 0)
        {
            foreach (var child in resource.Children)
            {
                if (child.ResourceType == ResourceType.Module || child.ResourceType == ResourceType.Page || child.ResourceType == ResourceType.Element)
                {
                    CleanResourceTree(child);
                }
            }

            resource.AccessRules = null;
            resource.Children = resource.Children.Where(x => x.ResourceType == ResourceType.Module || x.ResourceType == ResourceType.Page || x.ResourceType == ResourceType.Element).ToList();
        }
    }
    /// <summary>
    /// 从用户上下文得到客户端需要的用户信息
    /// </summary>
    /// <param name="userContext"></param>
    /// <param name="httpContext"></param>
    /// <returns></returns>
    public static UserInfo? GetUserInfo(this UserContext userContext, HttpContext httpContext)
    {
        var userName = httpContext.GetUserName();
        string system = httpContext.Request.GetRequestOption<string>(nameof(system)) ?? httpContext.Request.Host.Value;
        system = system.ToLowerInvariant();
        var top = userContext.ResourceTrees.Values.GetTopResource(system);
        top?.CleanResourceTree();

        var resources = top?.Children;
        if (resources is not null && resources.Count == AppSettings.UNIT_ONE && resources.First().Children?.Count != AppSettings.UNIT_ZERO)
        {
            //仅有一个大菜单且子菜单数量不为 0 时, 将子菜单展开
            resources = resources.FirstOrDefault()?.Children;
        }

        return new UserInfo { User = userContext.User.Desensitize(), Resources = resources };
    }

    /// <summary>
    /// 刷新用户授权相关缓存
    /// </summary>
    /// <returns></returns>
    public static async Task RefreshUserInfoAsync(this AuthorizationService authorizationService, IMessageCommunicator communicator, IdentityType identityType, params string[] identities)
    {
        var users = await authorizationService.GetIdentityUsers(identityType, identities);
        if (users is not null && users.Count != 0)
        {
            foreach (var user in users)
            {
                //更新用户授权相关缓存
                await authorizationService.GetUserContextAsync(user.UserName, true);

                //强制推送客户端更新
                await communicator.RefreshCacheAsync(nameof(UserInfo), user.UserName);
            }
        }
    }

    /// <summary>
    /// 获取身份相关调用错误提示
    /// </summary>
    /// <param name="errors"></param>
    /// <returns></returns>
    public static string GetErrorMessage(this IdentityResult identityResult) => (!identityResult.Succeeded && identityResult.Errors.Any()) ? identityResult.Errors.Select(x => x.Description).ToString<string>() : identityResult.ToString();

    /// <summary>
    /// 获取用户权限树的顶级系统资源对象
    /// </summary>
    /// <param name="resourceViewModels"></param>
    /// <param name="system"></param>
    /// <returns></returns>
    public static ResourceViewModel? GetTopResource(this ICollection<ResourceViewModel> resourceViewModels, string system)
    {
        var top = resourceViewModels.FirstOrDefault(x => x.ResourceType == ResourceType.System && x.Content == system);
        if (top is not null)
        {
            return top.Copy();
        }

        foreach (var resource in resourceViewModels)
        {
            if (resource.ResourceType == ResourceType.System && (resource.Children?.Any() ?? false))
            {
                top = resource.Children.GetTopResource(system);
                if (top is not null)
                {
                    return top.Copy(); ;
                }
            }
        }

        return null;
    }
    /// <summary>
    /// 用户上下文脱敏
    /// </summary>
    /// <param name="userContext"></param>
    /// <returns></returns>
    public static UserContext Desensitize(this UserContext userContext)
    {
        userContext.User.Desensitize();
        return userContext;
    }
    /// <summary>
    /// 用户信息脱敏
    /// </summary>
    /// <param name="userViewModel"></param>
    /// <returns></returns>
    public static UserViewModel Desensitize(this UserViewModel userViewModel)
    {
        userViewModel.Name = userViewModel.Name.Desensitize(AppSettings.UNIT_ONE, AppSettings.UNIT_ONE);
        userViewModel.PhoneNumber = userViewModel.PhoneNumber.Desensitize();
        userViewModel.Email = userViewModel.Email.Desensitize();
        return userViewModel;
    }
    /// <summary>
    /// 获取脱敏后的认证用户信息
    /// </summary>
    /// <param name="authUser"></param>
    /// <returns></returns>
    public static RealNameInfo Desensitize(this RealNameInfo authUser)
    {
        authUser.PhoneNumber = authUser.PhoneNumber.Desensitize();
        authUser.IdCode = authUser.IdCode.Desensitize();
        return authUser;
    }
    /// <summary>
    /// 获取脱敏后的认证用户信息视图模型
    /// </summary>
    /// <param name="authUser"></param>
    /// <returns></returns>
    public static RealNameViewModel Desensitize(this RealNameViewModel authUser)
    {
        authUser.PhoneNumber = authUser.PhoneNumber.Desensitize();
        authUser.IdCode = authUser.IdCode.Desensitize();
        return authUser;
    }

    /// <summary>
    /// 获取当前用户上下文的数据隔离编号
    /// </summary>
    /// <param name="userContext"></param>
    /// <param name="dataIsolateLevel"></param>
    public static string? GetDataIsolateId(this UserContext userContext, DataIsolateLevel dataIsolateLevel)
    {
        if (dataIsolateLevel != DataIsolateLevel.None && !userContext.IsAuthorizer)
        {
            switch (dataIsolateLevel)
            {
                case DataIsolateLevel.Individual:
                    return userContext.UserId;
                case DataIsolateLevel.Subordinate:
                    if (!string.IsNullOrWhiteSpace(userContext.User.GroupId))
                    {
                        return userContext.User.GroupId;
                    }
                    break;
                case DataIsolateLevel.TopGroup:
                    if (!string.IsNullOrWhiteSpace(userContext.User.GroupId))
                    {
                        var topGroup = userContext.Groups.GetTopParent<GroupViewModel>(userContext.User.GroupId);
                        if (topGroup is not null)
                        {
                            return topGroup.Id;
                        }
                    }
                    break;
                default:
                    break;
            }
        }

        return null;
    }
    /// <summary>
    /// 根据用户上下文获取自定义 claims
    /// </summary>
    /// <param name="user"></param>
    /// <param name="context"></param>
    /// <param name="httpSetting"></param>
    /// <returns></returns>
    public static async Task<List<Claim>> GetUserClaimsAsync(this UserViewModel user, IdentityDbContext context, DataIsolateLevel dataIsolateLevel)
    {
        ArgumentNullException.ThrowIfNull(user.Name);
        ArgumentNullException.ThrowIfNull(user.UserName);

        var claims = new List<Claim>{
            new(JwtClaimTypes.Name, user.Name),
            new(AppSettings.CLAIM_TYPE_USER_ID, user.Id),
            new(AppSettings.CLAIM_TYPE_USER_NAME, user.UserName),
        };

        if (!string.IsNullOrWhiteSpace(user.Alias))
        {
            claims.Add(new Claim(nameof(Identity.User.Alias).ToCamelCase(), user.Alias));
        }

        if (!string.IsNullOrWhiteSpace(user.Image))
        {
            claims.Add(new Claim(nameof(Identity.User.Image).ToCamelCase(), user.Image));
        }

        if (!string.IsNullOrWhiteSpace(user.GroupId))
        {
            claims.Add(new Claim(AppSettings.CLAIM_TYPE_GROUP_ID, user.GroupId));
        }

        if (dataIsolateLevel != DataIsolateLevel.None && user.Type < AuthorizeType.Authorizer)
        {
            switch (dataIsolateLevel)
            {
                case DataIsolateLevel.Individual:
                    claims.Add(new Claim(AppSettings.CLAIM_TYPE_DATA_ISOLATE_ID, user.Id));
                    break;
                case DataIsolateLevel.Subordinate:
                    if (!string.IsNullOrWhiteSpace(user.GroupId))
                    {
                        claims.Add(new Claim(AppSettings.CLAIM_TYPE_DATA_ISOLATE_ID, user.GroupId));
                    }
                    break;
                case DataIsolateLevel.TopGroup:
                    if (!string.IsNullOrWhiteSpace(user.GroupId))
                    {
                        var topGroup = await context.GetTopParentAsync<Group>(user.GroupId);
                        if (topGroup is not null)
                        {
                            claims.Add(new Claim(AppSettings.CLAIM_TYPE_DATA_ISOLATE_ID, topGroup.Id));
                        }
                    }
                    break;
                default:
                    break;
            }
        }

        return claims;
    }
}